#pragma once

#include <algorithm/analytical_model/algorithm/cpu/GeometryUtils.h>
#include <algorithm/analytical_model/algorithm/cuda/VectorMath.cuh>
#include <algorithm/analytical_model/algorithm/cuda/inter_op/Data.h>
#include <utils/Utils.h>
#include <utils/config.h>

#include <tuple>
#include <type_traits>

using namespace solar;
using namespace solar::cuda;

namespace solar::cuda
{
    __host__ __device__ auto localToWorldRotate(const float3& d_local,
                                                const float3& aligned_normal) -> float3;

    template <typename T>
    __host__ __device__ inline auto translate(const T& d_in, const T translate_vector) -> T
    {
#if defined(__CUDA_ARCH__)
        return d_in + translate_vector;
#else
        if constexpr (std::is_same_v<float3, T>)
        {
            auto result =
                solar::cpu::translate(structCopy<solar::cuda::inter_op::Float3>(d_in),
                                      structCopy<solar::cuda::inter_op::Float3>(translate_vector));
            return structCopy<float3>(result);
        }
        return d_in + translate_vector;
#endif
    }

    __host__ __device__ auto rotateY(const float3& origin, const float3& old_dir,
                                     const float3& new_dir) -> float3;

    /// @param dir1 微定日镜中点坐标
    /// @param normal 入射光向量
    /// @param refract 入射光径向偏移
    /// @param theta_1 折射率1
    /// @param theta_2 折射率2
    /// @return 折射光线方向
    __host__ __device__ auto refractRay(const float3& dir1, const float3& normal, float refract,
                                        float theta_1, float theta_2) -> float3;

    // Judge whether the ray intersects with the parallelogram (rectangle)
    /// @return {[float]t, [float]u, [float]v, [bool]intersects_or_not}
    /* Reference: Fast, Minimum Storage Ray Triangle Intersection
     * ray equation: orig + dir * t  (origin: orig, direction: dir)
     * A/B/C are three vertexes of the parallelogram and A is the angle subtend diagonal.
     * In rectangle, A = PI/2
     * orig + dir * t = (1-u-v)A + uB + vC
     *                   |t|
     * ==> |-D  B-A C-A| |u| = orig - A
     *                   |v|
     * Now use Cramer's Rule and the mixed product of vectors to solve the system :
     * Make E1 = B - A, E2 = C - A, T = orig - A
     * According to Cramer's Rule:
     *
     *       t =   _____1_____  |T E1 E2|
     *              |-D E1 E2|
     *
     *       U =   _____1_____  |-D T E2|
     *              |-D E1 E2|
     *
     *       V =   _____1_____  |-D E1 T|
     *              |-D E1 E2|
     *
     *  Combine these three solutions together:
     *      |t|                 | T E1 E2|
     *      |u| =  _____1_____  |-D  T E2|
     *      |v|     |-D E1 E2|  |-D E1  T|
     *  According to the mixed product of vectors: |a b c| = a x b · c = -a x c · b
     *
     *  it can be changed in this way:
     *      |t|                     |T x E1 · E2|
     *      |u| =   ______1______   |D x E2 · T |
     *      |v|     |D x E1 · E2|   |T x E1 · D |
     *  Make P = D x E2
     *       Q = T x E1
     *  Get the final formula:
     *      |t|                |Q · E2|
     *      |u| =   ___1____   |P · T |
     *      |v|     |P · E1|   |Q · D |
     */
    // It's better to return std::expected (requires C++ 23)
    __host__ __device__ auto
    rayParallelogramIntersect(const float3& orig, const float3& dir, const float3& A,
                              const float3& B,
                              const float3& C) -> std::tuple<float, float, float, bool>;

    __host__ __device__ auto intersect(const float3& normal, const float3& plane_origin,
                                       const float3& vertex,
                                       const float3& direction) -> std::tuple<float3, double>;
} // namespace solar::cuda

#if defined(SOLAR_CUDA_STANDALONE_COMPILE) && !defined(SOLAR_BUILD_GEOMETRY_UTILS)
    #define SOLAR_CUDA_INLINE inline
    #include "GeometryUtils.cu"
#endif
